﻿using CommonDevelop.Cache;
using CommonFP;
using System;
using System.Threading.Tasks;

namespace CommonDevelop
{
    /// <summary>
    /// 合理缓存
    /// </summary>
    public class ReasonableCache<T> where T : class
    {
        private readonly Func<string, Task<Validation<T>>> get_En_HOF;
        private readonly IDCSCache cache;
        private string cacheKey = "Reasonable_" + typeof(T).Name + "_";
        private readonly Func<string, T, ValueTuple> Add;   //property 定义：类的属性(通过参数提升，衔接行为，完成调用)
        private readonly Func<string, T, bool> Replace;
        /// <summary>
        /// method 定义：类的方法（常规，表示定义的内容不会被编译，需要指定提升才能配合函数式使用）
        /// </summary>
        private Option<T> Lookup(string id)
           => this.cache == null ? FP.None : this.cache.GetAsync<T>(this.cacheKey + id).Result.ToOption();

        private T Get_DB_To_Cache(string Id)
            => this.Get_DB_HOF(Id).Pipe(t => { this.Add.Apply(Id)(t); });

        private T Get_DB_HOF(string Id)
            => this.get_En_HOF.ToOption()
            .Map(f => f(Id).Recover(e => FP.Invalid(e.Message)))
            .Do(t => t.Wait())
            .Bind(t => FP.Some(t.Result.Outcome(x => x)))
            .Do(tp => this.Error = tp.Errors)
            .Bind(tp => FP.Some(tp.result))
            .Outcome();

        public string Error { get; set; }
        /// <summary>
        /// 构造
        /// </summary>
        public ReasonableCache(IDCSCache Cache, Func<string, Task<Validation<T>>> HOF)
        {
            this.cache = Cache;
            this.get_En_HOF = HOF;
            if (this.cache != null)
            {
                this.Add = (Id, t) => t.ToOption().Match(() => FP.Unit(), a => this.cache.Add(this.cacheKey + Id, a));
                this.Replace = (Id, t) => Id.ToOption()    //行为内容，对象化存在
                .Map(a => this.cache.Exists(this.cacheKey + a))
                .Map(a => a ? this.cache.Replace(this.cacheKey + Id, t) : this.cache.Add(this.cacheKey + Id, t))
                .Outcome();
            }
            else
            {
                this.Add = (Id, t) => FP.Unit();
                this.Replace = (Id, t) => false;
            }
        }

        public T GetModel(string Id)
            => this.Lookup(Id).GetOrElse(() => this.Get_DB_To_Cache(Id));

        public bool ReplaceModel(string Id, T t)
            => this.Replace(Id, t);
    }
}
